package controller

import (
	"bytes"
	"context"
	"encoding/json"
	"gitee.com/kristas/booting-go/framework/web"
	"io"
	"mime/multipart"
	"net/http"
	"reflect"
)

type ParamMapper interface {
	GetStringFromPath(name string) (string, error)
	GetStringFromQuery(name string) (string, error)
	GetStringFromHeader(name string) (string, error)
	BindData(param web.Param) (interface{}, error)
	GetStringFromForm(name string) (string, error)
	GetMapFromForm() (map[string][]string, error)
	GetMultipartHeader(name string) (*multipart.FileHeader, error)
	GetMultipartFile(name string) (multipart.File, error)
	GetDataFromMultipart(name string) ([]byte, error)
	GetRequest() *http.Request
	GetResponseWriter() http.ResponseWriter
	GetContext() context.Context
}

type paramMapper struct {
	adapter web.ParamAdapter
	req     *http.Request
	writer  http.ResponseWriter
}

func (p *paramMapper) GetStringFromPath(name string) (string, error) {
	return p.adapter.GetStringFromPath(name)
}

func (p *paramMapper) GetStringFromQuery(name string) (string, error) {
	return p.GetRequest().URL.Query().Get(name), nil
}

func (p *paramMapper) GetStringFromHeader(name string) (string, error) {
	return p.GetRequest().Header.Get(name), nil
}

func (p *paramMapper) BindData(param web.Param) (interface{}, error) {
	v := reflect.New(param.GetType()).Interface()
	req := p.GetRequest()
	data, err := io.ReadAll(req.Body)
	if err != nil {
		return nil, err
	}
	req.Body = io.NopCloser(bytes.NewReader(data))
	err = json.Unmarshal(data, v)
	if err != nil {
		return nil, err
	}
	return reflect.ValueOf(v).Elem().Interface(), nil
}

func (p *paramMapper) GetStringFromForm(name string) (string, error) {
	return p.GetRequest().FormValue(name), nil
}

func (p *paramMapper) GetMapFromForm() (map[string][]string, error) {
	err := p.GetRequest().ParseMultipartForm(int64(10 << 20))
	if err != nil {
		return nil, err
	}
	return p.GetRequest().PostForm, nil
}

func (p *paramMapper) GetMultipartHeader(name string) (*multipart.FileHeader, error) {
	_, header, err := p.GetRequest().FormFile(name)
	return header, err
}

func (p *paramMapper) GetMultipartFile(name string) (multipart.File, error) {
	file, _, err := p.GetRequest().FormFile(name)
	return file, err
}

func (p *paramMapper) GetDataFromMultipart(name string) ([]byte, error) {
	header, err := p.GetMultipartHeader(name)
	if err != nil {
		return nil, err
	}
	file, err := header.Open()
	if err != nil {
		return nil, err
	}
	data, err := io.ReadAll(file)
	if err != nil {
		return nil, err
	}
	return data, nil
}

func (p *paramMapper) GetRequest() *http.Request {
	return p.req
}

func (p *paramMapper) GetResponseWriter() http.ResponseWriter {
	return p.writer
}

func (p *paramMapper) GetContext() context.Context {
	return p.GetRequest().Context()
}

func newParamMapper(adapter web.ParamAdapter) ParamMapper {
	return &paramMapper{
		adapter: adapter,
		req:     adapter.GetRequest(),
		writer:  adapter.GetResponseWriter(),
	}
}
